<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8">
		<title>call-apply-bind的实现</title>
	</head>
	<body>
		<script type="text/javascript">
			// 1.call的使用  call(ctx,...args) ctx:执行函数需要改变的this对象，args:执行函数需要传入的参数 call改变函数this指向
			function fn(name) {
			  console.log(this, name); // window 老王
			}
			fn('老王');
			// 创建一个 obj
			let obj = {name: '老李'}
			// 通过call执行
			fn.call(obj, '我是谁') // {name: "老李"} "我是谁"  [注：如果有多个参数在后面接着即可]
			
			// 2.apply的使用 apply(ctx, array) ctx：执行函数的this指向; array：传入一个数组，我将数组的每一项作为函数的参数
			function fn1(name1, name2) {
			  console.log(this, name1, name2);
			}
			let obj1 = {name: '老王'};
			let arr = ['小王', '小明'];
			fn1.apply(obj1, arr) // {name: "老王"} "小王" "小明"
			// 小结: call传入的是不定参数，apply传入的是一个具体的数组，数组里面包含所有参数
			// 例子：求数组中最大值
			let list = [1,2,3,4,9,20,6,8],
				max = Math.max.apply(null, list);
				console.log(max) // => 20
			// 3.bind的使用
			// bind(ctx, arg...): bind 和call方法的参数是一样的，只是用法有些区别
			// bind执行后返回值是一个函数，
			// 这个函数的this被定死(绑定)为传入的ctx对象且不可变，
			// 这个函数的参数也被定死(绑定)为 ...arg且不可变
			function fn3(age) {
			  console.log(this, age)
			}
			let newoObj = {name: '小明'}
			let fun = fn3.bind(newoObj, 18)
			fun() // {name: "小明"} 18
			// 参数和this都被绑死
			fun.bind(window, 18)() // {name: "小明"} 18
			fun.call(window, 18) // {name: "小明"} 18
			fun.apply(window, [18]) // {name: "小明"} 18
			fun(18) // => {name: "小明"} 18
			// 小结：通过bind绑定过后的方法，无论怎么执行都无法修改this指向和参数的,注：以上三个方法如果不传第一个参数，this指向在非严格模式下均为window
			
			// 面试题:
			function fn() {
			  console.log(this)
			}
			let objtest = {name: '老王'}
			
			fn.call(objtest) // {name: "老王"} call改变函数this指向
			
			fn.call.call(function () {
			  console.log(this) // window
			}) 
			
			fn.call.call.call.call(function () {
			  console.log(this) // {name: "老王"}
			}, objtest) 
			/*****************************************************/
			// 1、手写call （1）改变this指向  （2）处理传入的参数
			Function.prototype.myCall = function(ctx){
				// 先将fn挂在context上
				ctx.fn = this
				var args = [...arguments].slice(1)
				var res = ctx.fn(...args)
				// ctx调用fn 使fn中的this指向到ctx上
				delete ctx.fn
				return res
			}
			// 2、手写apply （1）改变this指向  （2）参数通过数组的形式
			Function.prototype.myApply = function(ctx){
				// 先将fn挂在context上
				ctx.fn = this
				var res
				if(arguments[1]){
					res = [...arguments[1]];
				} else {
					res = ctx.fn;
				}
				delete ctx.fn
				return res
			}
			// 3、手写bind  bind终生绑定不立即执行返回一个新函数，传参和 call 是一样的
			Function.prototype.myBind = function(context) {
			    if (typeof this !== 'function') {
			        throw new TypeError('Error')
			    }
			    // 返回一个绑定this的函数，这里我们需要保存this
			    const _this = this
			    const args = [...arguments].slice(1)
			    //返回一个函数
			    return function F() {
			        // 因为返回一个函数，我们可以new F()需要判断能当做构造函数吗
			        if (this instanceof F) {
			            return new _this(...args, ...arguments)
			        }
			        return _this.apply(context, args.concat(...arguments))
			    }
			}
			let testObj = {
				name: '测试手写'
			}
			let testArr = ['小王', '小明'];
			function test(){
				console.log(this, name);
			}
			
			test.myCall(testObj) // {name: "测试手写", fn: ƒ}
			
			function test2(name1, name2) {
			  console.log(this, name1, name2);
			}
			
			test2.apply(testObj,testArr) // {name: "测试手写"} "小王" "小明"
			
			function foo() {
			    console.log(this.name) // 测试手写
			    console.log(arguments) // ['a','b','c']
			}
			
			foo.myBind(testObj, 'a', 'b', 'c')() 
		</script>
	</body>
</html>
